/*
 * Copyright (c) 2024-2025 LTC-CN. All rights reserved.
 * <p>
 * This file is part of the ltc-cn-springboot project.
 * <p>
 * ltc-cn-springboot is distributed under the terms of the MIT License.
 * For details, see the accompanying LICENSE file or visit:
 * <a href="https://www.ltc-cn.top/projects">...</a>
 */

package top.ltc_cn.springboot_tools.tools.passwordUtils;

import io.micrometer.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.stereotype.Component;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;

/**
 * @ClassName SM2Utils
 * @Description SM2算法工具类，没有线程池，面对低频场景，若高频请使用线程池处理
 */


@Slf4j
@Component
public class Sm2Utils {

    private Sm2Utils() {} // 防止实例化

    private static final X9ECParameters X9_EC_PARAMETERS = GMNamedCurves.getByName("sm2p256v1");
    private static final ECParameterSpec EC_PARAMETER_SPEC = new ECParameterSpec(
            X9_EC_PARAMETERS.getCurve(),
            X9_EC_PARAMETERS.getG(),
            X9_EC_PARAMETERS.getN());

    private static final int SM2_MAX_PLAINTEXT_LENGTH = 255; // SM2明文最大长度

    /**
     * 生成SM2密钥对(带校验)
     */
    public static KeyPair generateKeyPair() {
        try {
            KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
            kpg.initialize(new ECGenParameterSpec("sm2p256v1"), SecureRandom.getInstanceStrong());

            KeyPair keyPair = kpg.generateKeyPair();
            // 验证生成的密钥
            validateKeyPair(keyPair);
            return keyPair;
        } catch (Exception e) {
            log.error("SM2密钥对生成失败", e);
            throw new CryptoException("SM2密钥对生成失败", e);
        }
    }

    /**
     * SM2加密(推荐使用C1C3C2模式)
     * @param publicKeyHex 04开头的压缩公钥(130字节16进制字符串)
     * @param data 明文数据
     * @param mode 加密模式(1-C1C3C2, 其他-C1C2C3)
     * @return 密文16进制字符串
     */
    public static String encrypt(String publicKeyHex, String data, int mode) {
        if (StringUtils.isBlank(publicKeyHex) || StringUtils.isBlank(data)) {
            throw new IllegalArgumentException("公钥和明文不能为空");
        }
        if (data.length() > SM2_MAX_PLAINTEXT_LENGTH) {
            throw new IllegalArgumentException("明文长度超过SM2限制");
        }

        BCECPublicKey publicKey = parsePublicKey(publicKeyHex);
        SM2Engine.Mode engineMode = (mode == 1) ? SM2Engine.Mode.C1C3C2 : SM2Engine.Mode.C1C2C3;

        try {
            SM2Engine engine = new SM2Engine(engineMode);
            ECPublicKeyParameters pubKeyParams = new ECPublicKeyParameters(
                    publicKey.getQ(),
                    new ECDomainParameters(
                            X9_EC_PARAMETERS.getCurve(),
                            X9_EC_PARAMETERS.getG(),
                            X9_EC_PARAMETERS.getN()));

            engine.init(true, new ParametersWithRandom(pubKeyParams, new SecureRandom()));

            byte[] encrypted = engine.processBlock(
                    data.getBytes(StandardCharsets.UTF_8),
                    0,
                    data.getBytes(StandardCharsets.UTF_8).length);

            return Hex.toHexString(encrypted);
        } catch (Exception e) {
            log.error("SM2加密失败: publicKey={}, data={}", publicKeyHex, data, e);
            throw new CryptoException("SM2加密失败", e);
        }
    }

    /**
     * SM2解密
     */
    public static String decrypt(String privateKeyHex, String cipherData, int mode) {
        if (StringUtils.isBlank(privateKeyHex) || StringUtils.isBlank(cipherData)) {
            throw new IllegalArgumentException("私钥和密文不能为空");
        }

        BCECPrivateKey privateKey = parsePrivateKey(privateKeyHex);
        SM2Engine.Mode engineMode = (mode == 1) ? SM2Engine.Mode.C1C3C2 : SM2Engine.Mode.C1C2C3;

        try {
            SM2Engine engine = new SM2Engine(engineMode);
            ECPrivateKeyParameters priKeyParams = new ECPrivateKeyParameters(
                    privateKey.getD(),
                    new ECDomainParameters(
                            X9_EC_PARAMETERS.getCurve(),
                            X9_EC_PARAMETERS.getG(),
                            X9_EC_PARAMETERS.getN()));

            engine.init(false, priKeyParams);

            byte[] decrypted = engine.processBlock(
                    Hex.decode(cipherData),
                    0,
                    Hex.decode(cipherData).length);

            return new String(decrypted, StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.error("SM2解密失败: cipherData={}", cipherData, e);
            throw new CryptoException("SM2解密失败", e);
        }
    }

    /**
     * 解析公钥(带校验)
     */
    private static BCECPublicKey parsePublicKey(String pubKeyHex) {
        try {
            // 去除可能存在的04前缀
            String hex = pubKeyHex.startsWith("04") ? pubKeyHex.substring(2) : pubKeyHex;
            if (hex.length() != 128) {
                throw new IllegalArgumentException("无效的公钥长度");
            }

            String stringX = hex.substring(0, 64);
            String stringY = hex.substring(64);

            BigInteger x = new BigInteger(stringX, 16);
            BigInteger y = new BigInteger(stringY, 16);

            ECPoint point = X9_EC_PARAMETERS.getCurve().createPoint(x, y);
            // 验证点是否在曲线上
            if (!point.isValid()) {
                throw new IllegalArgumentException("公钥点不在SM2曲线上");
            }

            return new BCECPublicKey(
                    "EC",
                    new ECPublicKeySpec(point, EC_PARAMETER_SPEC),
                    BouncyCastleProvider.CONFIGURATION);
        } catch (Exception e) {
            log.error("SM2公钥解析失败: {}", pubKeyHex, e);
            throw new CryptoException("SM2公钥解析失败", e);
        }
    }

    /**
     * 解析私钥
     */
    private static BCECPrivateKey parsePrivateKey(String privateKeyHex) {
        try {
            BigInteger d = new BigInteger(privateKeyHex, 16);
            return new BCECPrivateKey(
                    "EC",
                    new ECPrivateKeySpec(d, EC_PARAMETER_SPEC),
                    BouncyCastleProvider.CONFIGURATION);
        } catch (Exception e) {
            log.error("SM2私钥解析失败", e);
            throw new CryptoException("SM2私钥解析失败", e);
        }
    }

    /**
     * 验证密钥对是否有效
     */
    private static void validateKeyPair(KeyPair keyPair) {
        if (!(keyPair.getPublic() instanceof BCECPublicKey publicKey)) {
            throw new CryptoException("无效的SM2公钥类型");
        }
        if (!(keyPair.getPrivate() instanceof BCECPrivateKey)) {
            throw new CryptoException("无效的SM2私钥类型");
        }

        if (!publicKey.getQ().isValid()) {
            throw new CryptoException("生成的公钥点不在SM2曲线上");
        }
    }

    /**
     * 自定义加密异常
     */
    public static class CryptoException extends RuntimeException {
        public CryptoException(String message) {
            super(message);
        }

        public CryptoException(String message, Throwable cause) {
            super(message, cause);
        }
    }

//    public static void main(String[] args) {
//        String publicKeyHex = null;
//        String privateKeyHex = null;
//        KeyPair keyPair = createECKeyPair();
//        PublicKey publicKey = null;
//        if (keyPair != null) {
//            publicKey = keyPair.getPublic();
//        }
//        if (publicKey instanceof BCECPublicKey) {
//            //获取65字节非压缩缩的十六进制公钥串(0x04)
//            publicKeyHex = Hex.toHexString(((BCECPublicKey) publicKey).getQ().getEncoded(false));
//            System.out.println("SM2公钥：" + publicKeyHex);
//        }
//        PrivateKey privateKey = null;
//        if (keyPair != null) {
//            privateKey = keyPair.getPrivate();
//        }
//        if (privateKey instanceof BCECPrivateKey) {
//            //获取32字节十六进制私钥串
//            privateKeyHex = ((BCECPrivateKey) privateKey).getD().toString(16);
//            System.out.println("SM2私钥：" + privateKeyHex);
//        }
//
//        /**
//         * 公钥加密
//         */
//        String data = "=========需要加密的数据=========";
//
//        //将十六进制公钥串转换为 BCECPublicKey 公钥对象
//        String encryptData = encrypt(publicKeyHex, data);
//        System.out.println("加密结果：" + encryptData);
//
//        /**
//         * 私钥解密
//         */
//        //将十六进制私钥串转换为 BCECPrivateKey 私钥对象
//        data = decrypt(privateKeyHex, encryptData);
//        System.out.println("解密结果：" + data);
//    }
}
